//******************************************************************************
//
// File:    PJGImage.java
// Package: benchmarks.detinfer.pj.edu.ritimage
// Unit:    Class benchmarks.detinfer.pj.edu.ritimage.PJGImage
//
// This Java source file is copyright (C) 2008 by Alan Kaminsky. All rights
// reserved. For further information, contact the author, Alan Kaminsky, at
// ark@cs.rit.edu.
//
// This Java source file is part of the Parallel Java Library ("PJ"). PJ is free
// software; you can redistribute it and/or modify it under the terms of the GNU
// General Public License as published by the Free Software Foundation; either
// version 3 of the License, or (at your option) any later version.
//
// PJ is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
// A PARTICULAR PURPOSE. See the GNU General Public License for more details.
//
// A copy of the GNU General Public License is provided in the file gpl.txt. You
// may also obtain a copy of the GNU General Public License on the World Wide
// Web at http://www.gnu.org/licenses/gpl.html.
//
//******************************************************************************

package benchmarks.detinfer.pj.edu.ritimage;

import benchmarks.detinfer.pj.edu.ritswing.Displayable;

import benchmarks.detinfer.pj.edu.ritutil.Range;

import java.awt.image.BufferedImage;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;

/**
 * Class PJGImage is the abstract base class for an image that is read from or
 * written to a file in Parallel Java Graphics (PJG) format. PJG image files are
 * designed to be generated by parallel programs.
 * <P>
 * Compared to PNG files, PJG files use a compression algorithm that yields
 * somewhat larger file sizes but has a much smaller running time. In one test
 * of a set of 24-bit color images, the PJG file sizes were 2-17% of the raw
 * data sizes, whereas the PNG file sizes were 2-11% of the raw data sizes. The
 * PJG file sizes were 30-50% larger than the corresponding PNG file sizes.
 * However, the time to write the PJG files was only 1/10th to 1/20th the time
 * to write the PNG files. This small running time to write a PJG file can
 * substantially reduce a parallel program's sequential fraction, with a
 * corresponding increase in the program's scalability.
 * <P>
 * In addition, sections of an image can be written to a PJG image file in
 * arbitrary order. Sections of an image can also be scattered across multiple
 * files. This lets the processes of a cluster parallel program generate
 * portions of an image independently, without needing to gather the whole image
 * into one process.
 * <P>
 * The {@linkplain PJG} program can be used to display an image stored in one
 * PJG file or scattered among multiple PJG files; to save the displayed image
 * in a single PJG file; and to save the image in a PNG file or PostScript file.
 * <P>
 * <B>Usage</B>
 * <P>
 * To create a PJGImage object, instantiate one of the subclasses of class
 * PJGImage, depending on the type of image. The image's pixel data is stored in
 * an external matrix, as described in the documentation for each subclass. The
 * available subclasses are:
 * <UL>
 * <LI>
 * {@linkplain PJGColorImage} -- 24-bit color image, best when the image has a
 * small number of discrete colors
 * <LI>
 * {@linkplain PJGHueImage} -- 24-bit hue image, best when the image has a
 * continuous range of hues
 * <LI>
 * {@linkplain PJGGrayImage} -- 8-bit grayscale image
 * </UL>
 * Subclasses for other types of images will be added in later releases.
 * <P>
 * To get and set the image's pixel data, use methods in the subclass. You only
 * need to allocate storage in the pixel data matrix for the portions of the
 * image you are actually accessing; the complete matrix need not be allocated.
 * Class {@linkplain benchmarks.detinfer.pj.edu.ritutil.Arrays} has static methods for allocating
 * portions of a matrix.
 * <P>
 * To write a PJGImage object to a PJG image file, call the
 * <TT>prepareToWrite()</TT> method, specifying the output stream to write. The
 * <TT>prepareToWrite()</TT> method returns an instance of class {@linkplain
 * PJGImage.Writer}. Call the methods of the PJG image writer object to write
 * the pixel data, or sections of the pixel data, to the output stream. When
 * finished, close the PJG image writer.
 * <P>
 * To read a PJGImage object from a PJG image file, call the
 * <TT>prepareToRead()</TT> method, specifying the input stream to read. The
 * <TT>prepareToRead()</TT> method returns an instance of class {@linkplain
 * PJGImage.Reader}. Call the methods of the PJG image reader object to read
 * the pixel data, or sections of the pixel data, from the input stream. When
 * finished, close the PJG image reader.
 * <P>
 * The previous paragraph assumes you already have a PJGImage object which is an
 * instance of the correct subclass to hold the pixel data that will be read
 * from the PJG image file. To both create an instance of the correct PJGImage
 * subclass and read a PJG image file into that instance, call the static
 * <TT>readFromStream()</TT> method. To create an instance of the correct
 * PJGImage subclass to hold the contents of a PJG image file without actually
 * reading in the pixel data, call the static <TT>createFromStream()</TT>
 * method.
 * <P>
 * To get a BufferedImage object that uses the same underlying pixel data matrix
 * as the PJGImage object, call the <TT>getBufferedImage()</TT> method. You can
 * then do all the following with the BufferedImage: display it on the screen,
 * draw into it using a graphics context, copy another BufferedImage into it,
 * read it from or write it to a file using package javax.imageio (which
 * typically supports PNG, JPG, and GIF formats). The rows and columns of the
 * underlying matrix need not all be allocated when accessing the BufferedImage.
 * If you get a pixel from the BufferedImage in an unallocated row or column, a
 * pixel value of 0 (black) is returned. If you set a pixel in the BufferedImage
 * in an unallocated row or column, the pixel value is discarded.
 * <P>
 * <I>Note:</I> Class PJGImage is not multiple thread safe.
 * <P>
 * <B>PJG Image File Format</B>
 * <P>
 * A PJG image file consists of a sequence of <I>segments.</I> Each segment
 * consists of the following:
 * <UL>
 * <LI>
 * Segment type. Stored as a one-byte integer. Legal values:
 * <BR>0 = Header segment
 * <BR>1 = Image type segment
 * <BR>2 = Height segment
 * <BR>3 = Width segment
 * <BR>4 = Creation time segment
 * <BR>5 = Comment segment
 * <BR>6 = Run length encoded 24-bit color pixel data segment
 * <BR>7 = Huffman delta encoded 8-bit grayscale pixel data segment
 * <BR>8 = Huffman delta encoded 24-bit hue pixel data segment
 * <P><LI>
 * Segment contents. Depends on the type of segment, as described below.
 * </UL>
 * <P>
 * <B>Header segment</B> (segment type 0). The first segment in the file must be
 * a header segment. There must be exactly one header segment in the file. The
 * segment contents are:
 * <UL>
 * <LI>
 * One byte with the value 0x50.
 * <P><LI>
 * One byte with the value 0x4A.
 * <P><LI>
 * One byte with the value 0x47.
 * <P><LI>
 * PJG file format version number. Stored as a four-byte integer, most
 * significant byte first. Currently the PJG file format version number is 1.
 * </UL>
 * Thus, the first eight bytes of a PJG image file must be 0x00, 0x50, 0x4A,
 * 0x47, 0x00, 0x00, 0x00, 0x01.
 * <P>
 * <B>Image type segment</B> (segment type 1). The image type segment must come
 * after the header segment and before any pixel data segments. There must be
 * exactly one image type segment in the file. The segment contents are:
 * <UL>
 * <LI>
 * Image type. Stored as a one-byte integer. Legal values:
 * <BR>0 = 24-bit color image
 * <BR>1 = 8-bit grayscale image
 * <BR>2 = 24-bit hue image
 * </UL>
 * <P>
 * <B>Height segment</B> (segment type 2). The height segment must come after
 * the header segment and before any pixel data segments. There must be exactly
 * one height segment in the file. The segment contents are:
 * <UL>
 * <LI>
 * Image height in pixels. Stored as a four-byte integer, most significant byte
 * first. The image height must be &gt; 0.
 * </UL>
 * <P>
 * <B>Width segment</B> (segment type 3). The width segment must come after the
 * header segment and before any pixel data segments. There must be exactly one
 * width segment in the file. The segment contents are:
 * <UL>
 * <LI>
 * Image width in pixels. Stored as a four-byte integer, most significant byte
 * first. The image width must be &gt; 0.
 * </UL>
 * <P>
 * <B>Creation time segment</B> (segment type 4). The creation time segment must
 * come after the header segment and before any pixel data segments. There must
 * be at most one creation time segment in the file. The segment contents are:
 * <UL>
 * <LI>
 * File creation time in milliseconds since midnight 01-Jan-1970 UTC. Stored as
 * an eight-byte integer, most significant byte first. (This is the return value
 * of the <TT>System.currentTimeMillis()</TT> method.)
 * </UL>
 * <P>
 * <B>Comment segment</B> (segment type 5). A comment segment must come after
 * the header segment and before any pixel data segments. There may be zero or
 * more comment segments in the file. The segment contents are:
 * <UL>
 * <LI>
 * Comment text, a string stored in the same format as the java.io.DataInput and
 * java.io.DataOutput interfaces use. This consists of the number of bytes in
 * the encoded string, stored as a two-byte integer, most significant byte
 * first, followed by the encoded string itself. The string is encoded using a
 * modified UTF-8 encoding.
 * </UL>
 * <P>
 * <B>Run length encoded 24-bit color pixel data segment</B> (segment type 6). A
 * run length encoded 24-bit color pixel data segment may appear only in a file
 * with image type = 0 (24-bit color image). A run length encoded 24-bit color
 * pixel data segment may appear anywhere in the file after the header, image
 * type, height, width, creation time, and comment segments. There may be zero
 * or more run length encoded 24-bit color pixel data segments in the file.
 * <P>
 * The pixel data segment contains pixel data for one <I>block</I> of the image.
 * The block encompasses certain rows and certain columns of the image (not
 * necessarily all the rows and columns). Blocks may appear in any order in the
 * file. The file need not contain pixel data for all the rows and columns in
 * the image.
 * <P>
 * There is an implicit <I>dictionary</I> that associates consecutive indexes 0,
 * 1, 2, . . . with pixel data values. The dictionary is not stored in the file;
 * rather, the contents of the dictionary are built up as the pixel data
 * segments are written or read. The dictionary persists between pixel data
 * segments and is used for all the pixel data segments in the file.
 * <P>
 * The first four fields of the pixel data segment are:
 * <UL>
 * <LI>
 * The block's upper left pixel's row index, in the range 0 .. image height - 1.
 * Stored as a four-byte integer, most significant byte first.
 * <P><LI>
 * The block's upper left pixel's column index, in the range 0 .. image width -
 * 1. Stored as a four-byte integer, most significant byte first.
 * <P><LI>
 * Number of rows in the block. Stored as a four-byte integer, most significant
 * byte first. The number of rows must be &gt; 0. The block's row index plus the
 * number of rows must be &lt;= the image height.
 * <P><LI>
 * Number of columns in the block. Stored as a four-byte integer, most
 * significant byte first. The number of columns must be &gt; 0. The block's
 * column index plus the number of columns must be &lt;= the image width.
 * </UL>
 * The pixel data for the block is encoded as a sequence of <I>runs.</I> Each
 * run consists of one or more consecutive pixels with identical values. The
 * runs are found by scanning the pixel rows from lowest to highest row index.
 * Within each row, the runs are found by scanning the pixels from lowest to
 * highest column index. A run ends at the end of a row, or when a different
 * pixel value is encountered.
 * <P>
 * Each run is encoded as follows. Let <I>n</I> be one less than the number of
 * pixels in the run. Let <I>p</I> be the 24-bit pixel value (8-bit red
 * component, 8-bit green component, 8-bit blue component) of the pixels in the
 * run. Let <I>x</I> be the index associated with pixel value <I>p</I> in the
 * dictionary. First encode the run length:
 * <UL>
 * <LI>
 * If <I>n</I> &lt;= 127, store one byte: 0<I>nnnnnnn</I>.
 * <P><LI>
 * If 128 &lt;= <I>n</I> &lt;= 32767, store two bytes: 1<I>nnnnnn nnnnnnn</I>.
 * <P><LI>
 * Otherwise, break the run into two or more runs each with <I>n</I> &lt;= 32767
 * and encode each run separately.
 * </UL>
 * Then encode the pixel value:
 * <UL>
 * <LI>
 * If <I>p</I> is in the dictionary and <I>x</I> &lt;= 127, store one byte:
 * 0<I>xxxxxxx</I>.
 * <P><LI>
 * If <I>p</I> is in the dictionary and 128 &lt;= <I>x</I> &lt;= 16383, store
 * two bytes: 10<I>xxxxxx xxxxxxxx</I>.
 * <P><LI>
 * If <I>p</I> is in the dictionary and 16384 &lt;= <I>x</I> &lt;= 2097151,
 * store three bytes: 110<I>xxxxx xxxxxxxx xxxxxxxx</I>.
 * <P><LI>
 * Otherwise, store four bytes: 11100000 <I>pppppppp pppppppp pppppppp</I>.
 * Also, if the dictionary has fewer than 2097152 entries, add <I>p</I> to the
 * dictionary, associating <I>p</I> with the next higher unused index <I>x</I>.
 * That is, the first <I>p</I> added to the dictionary is associated with index
 * 0, the second <I>p</I> added to the dictionary is associated with index 1,
 * and so on.
 * </UL>
 * <P>
 * <B>Huffman delta encoded 8-bit grayscale pixel data segment</B> (segment type
 * 7). A Huffman delta encoded 8-bit grayscale pixel data segment may appear
 * only in a file with image type = 1 (8-bit grayscale image). A Huffman delta
 * encoded 8-bit grayscale pixel data segment may appear anywhere in the file
 * after the header, image type, height, width, creation time, and comment
 * segments. There may be zero or more Huffman delta encoded 8-bit grayscale
 * pixel data segments in the file.
 * <P>
 * The pixel data segment contains pixel data for one <I>block</I> of the image.
 * The block encompasses certain rows and certain columns of the image (not
 * necessarily all the rows and columns). Blocks may appear in any order in the
 * file. The file need not contain pixel data for all the rows and columns in
 * the image.
 * <P>
 * The first four fields of the pixel data segment are:
 * <UL>
 * <LI>
 * The block's upper left pixel's row index, in the range 0 .. image height - 1.
 * Stored as a four-byte integer, most significant byte first.
 * <P><LI>
 * The block's upper left pixel's column index, in the range 0 .. image width -
 * 1. Stored as a four-byte integer, most significant byte first.
 * <P><LI>
 * Number of rows in the block. Stored as a four-byte integer, most significant
 * byte first. The number of rows must be &gt; 0. The block's row index plus the
 * number of rows must be &lt;= the image height.
 * <P><LI>
 * Number of columns in the block. Stored as a four-byte integer, most
 * significant byte first. The number of columns must be &gt; 0. The block's
 * column index plus the number of columns must be &lt;= the image width.
 * </UL>
 * Each pixel value is an integer from 0 (black) through 255 (white) inclusive.
 * <P>
 * Certain pixel data in each row of the block is encoded in the form of
 * <I>deltas</I> rather than the pixel values themselves. The delta in column
 * <I>i</I> is equal to the pixel value in column <I>i</I> minus the pixel value
 * in column <I>i</I>-1, except the delta in the block's first column is equal
 * to the pixel value in the block's first column.
 * <P>
 * The pixel data for the block is encoded by scanning the pixel rows from
 * lowest to highest row index. Within each row, the pixels are scanned from
 * lowest to highest column index. Each column is encoded using a modified
 * Huffman encoding as follows:
 * <UL>
 * <LI>
 * If the delta is 0, encode 1 bit: 0.
 * <BR>&nbsp;
 * <LI>
 * If the delta is in the range -2 through -1 inclusive, encode 4 bits:
 * 10<I>dd</I>, where <I>dd</I> is the delta as a signed two's complement
 * number.
 * <BR>&nbsp;
 * <LI>
 * If the delta is in the range +1 through +2 inclusive, encode 4 bits:
 * 10<I>dd</I>, where <I>dd</I> is (delta - 1) as a signed two's complement
 * number.
 * <BR>&nbsp;
 * <LI>
 * If the delta is in the range -10 through -3 inclusive, encode 7 bits:
 * 110<I>dddd</I>, where <I>dddd</I> is (delta + 2) as a signed two's complement
 * number.
 * <BR>&nbsp;
 * <LI>
 * If the delta is in the range +3 through +10 inclusive, encode 7 bits:
 * 110<I>dddd</I>, where <I>dddd</I> is (delta - 3) as a signed two's complement
 * number.
 * <BR>&nbsp;
 * <LI>
 * If the delta is in the range -42 through -11 inclusive, encode 10 bits:
 * 1110<I>dddddd</I>, where <I>dddddd</I> is (delta + 10) as a signed two's
 * complement number.
 * <BR>&nbsp;
 * <LI>
 * If the delta is in the range +11 through +42 inclusive, encode 10 bits:
 * 1110<I>dddddd</I>, where <I>dddddd</I> is (delta - 11) as a signed two's
 * complement number.
 * <BR>&nbsp;
 * <LI>
 * Otherwise, encode 12 bits: 1111<I>pppppppp</I>, where <I>pppppppp</I> is the
 * pixel value (not the delta).
 * </UL>
 * The above encoding process yields a bit string. Each group of 8 bits is
 * packed into a byte, from most significant bit to least significant bit. Any
 * unused least significant bits in the last byte are set to zero. The resulting
 * sequence of bytes forms the remainder of the pixel data segment.
 * <P>
 * <B>Huffman delta encoded 24-bit hue pixel data segment</B> (segment type 8).
 * A Huffman delta encoded 24-bit hue pixel data segment may appear only in a
 * file with image type = 2 (24-bit hue image). A Huffman delta encoded 24-bit
 * hue pixel data segment may appear anywhere in the file after the header,
 * image type, height, width, creation time, and comment segments. There may be
 * zero or more Huffman delta encoded 24-bit hue pixel data segments in the
 * file.
 * <P>
 * The pixel data segment contains pixel data for one <I>block</I> of the image.
 * The block encompasses certain rows and certain columns of the image (not
 * necessarily all the rows and columns). Blocks may appear in any order in the
 * file. The file need not contain pixel data for all the rows and columns in
 * the image.
 * <P>
 * The first four fields of the pixel data segment are:
 * <UL>
 * <LI>
 * The block's upper left pixel's row index, in the range 0 .. image height - 1.
 * Stored as a four-byte integer, most significant byte first.
 * <P><LI>
 * The block's upper left pixel's column index, in the range 0 .. image width -
 * 1. Stored as a four-byte integer, most significant byte first.
 * <P><LI>
 * Number of rows in the block. Stored as a four-byte integer, most significant
 * byte first. The number of rows must be &gt; 0. The block's row index plus the
 * number of rows must be &lt;= the image height.
 * <P><LI>
 * Number of columns in the block. Stored as a four-byte integer, most
 * significant byte first. The number of columns must be &gt; 0. The block's
 * column index plus the number of columns must be &lt;= the image width.
 * </UL>
 * Each 24-bit pixel value consists of an 8-bit red component, an 8-bit green
 * component, and an 8-bit blue component. Each component is an integer from 0
 * through 255 inclusive.
 * <P>
 * First, the red components of all the pixels are treated as an 8-bit grayscale
 * image and are encoded using Huffman delta encoding as described above,
 * yielding a bit string.
 * <P>
 * Second, the green components of all the pixels are treated as an 8-bit
 * grayscale image and are encoded using Huffman delta encoding as described
 * above, yielding a bit string.
 * <P>
 * Third, the blue components of all the pixels are treated as an 8-bit
 * grayscale image and are encoded using Huffman delta encoding as described
 * above, yielding a bit string.
 * <P>
 * Finally, the above three bit strings are concatenated yielding a longer bit
 * string. Each group of 8 bits is packed into a byte, from most significant bit
 * to least significant bit. Any unused least significant bits in the last byte
 * are set to zero. The resulting sequence of bytes forms the remainder of the
 * pixel data segment.
 *
 * @author  Alan Kaminsky
 * @version 08-Apr-2008
 */
public abstract class PJGImage
	{

// Hidden constants.

	static final byte[] HEADER = new byte[]
		{
		(byte) 0x00,
		(byte) 0x50,
		(byte) 0x4A,
		(byte) 0x47,
		(byte) 0x00,
		(byte) 0x00,
		(byte) 0x00,
		(byte) 0x01,
		};

	static final int SEGMENT_HEADER                      = 0;
	static final int SEGMENT_IMAGE_TYPE                  = 1;
	static final int SEGMENT_HEIGHT                      = 2;
	static final int SEGMENT_WIDTH                       = 3;
	static final int SEGMENT_CREATION_TIME               = 4;
	static final int SEGMENT_COMMENT                     = 5;
	static final int SEGMENT_PIXEL_DATA_RLE_24_BIT_COLOR = 6;
	static final int SEGMENT_PIXEL_DATA_HDE_8_BIT_GRAY   = 7;
	static final int SEGMENT_PIXEL_DATA_HDE_24_BIT_HUE   = 8;

	static final int IMAGE_TYPE_24_BIT_COLOR = 0;
	static final int IMAGE_TYPE_8_BIT_GRAY   = 1;
	static final int IMAGE_TYPE_24_BIT_HUE   = 2;

// Hidden data members.

	int myImageType;
	int myHeight;
	int myWidth;
	Long myCreationTime;
	LinkedList<String> myComments = new LinkedList<String>();

// Hidden constructors.

	/**
	 * Construct a new PJG image. The image's height and width are
	 * uninitialized. Before accessing the image's pixels, specify the height
	 * and width by calling a subclass method or by reading the image from an
	 * input stream.
	 *
	 * @param  theImageType  Image type.
	 */
	PJGImage
		(int theImageType)
		{
		myImageType = theImageType;
		}

// Exported operations.

	/**
	 * Returns this image's height.
	 *
	 * @return  Image height in pixels.
	 */
	public int getHeight()
		{
		return myHeight;
		}

	/**
	 * Returns this image's width.
	 *
	 * @return  Image width in pixels.
	 */
	public int getWidth()
		{
		return myWidth;
		}

	/**
	 * Returns this image's creation time.
	 *
	 * @return  Creation time in milliseconds since midnight 01-Jan-1970 UTC, or
	 *          null if creation time is not set.
	 */
	public Long getCreationTime()
		{
		return myCreationTime;
		}

	/**
	 * Set this image's creation time.
	 *
	 * @param  time  Creation time in milliseconds since midnight 01-Jan-1970
	 *               UTC. If null, the creation time is not set.
	 */
	public void setCreationTime
		(Long time)
		{
		myCreationTime = time;
		}

	/**
	 * Returns an iterable collection of the comments in this image. The
	 * returned collection is unmodifiable.
	 *
	 * @return  Comment strings.
	 */
	public Iterable<String> getComments()
		{
		return Collections.unmodifiableList (myComments);
		}

	/**
	 * Remove all of the comments in this image.
	 */
	public void clearComments()
		{
		myComments.clear();
		}

	/**
	 * Add the given comment to this image.
	 *
	 * @param  theComment  Comment string.
	 *
	 * @exception  NullPointerException
	 *     (unchecked exception) Thrown if <TT>theComment</TT> is null.
	 */
	public void addComment
		(String theComment)
		{
		myComments.add (theComment);
		}

	/**
	 * Prepare to write this image to the given output stream. Certain header
	 * information is written to the output stream at this time. To write this
	 * image's pixel data, call methods on the returned PJG image writer, then
	 * close the PJG image writer.
	 * <P>
	 * For improved performance, specify an output stream with buffering, such
	 * as an instance of class java.io.BufferedOutputStream.
	 *
	 * @param  theStream  Output stream.
	 *
	 * @return  PJG image writer object with which to write this image.
	 *
	 * @exception  NullPointerException
	 *     (unchecked exception) Thrown if <TT>theStream</TT> is null.
	 * @exception  IOException
	 *     Thrown if an I/O error occurred.
	 */
	public abstract PJGImage.Writer prepareToWrite
		(OutputStream theStream)
		throws IOException;

	/**
	 * Prepare to read this image from the given input stream. Certain header
	 * information is read from the input stream at this time. To read this
	 * image's pixel data, call methods on the returned PJG image reader, then
	 * close the PJG image reader.
	 * <P>
	 * For improved performance, specify an input stream with buffering, such
	 * as an instance of class java.io.BufferedInputStream.
	 *
	 * @param  theStream  Input stream.
	 *
	 * @return  PJG image reader object with which to read this image.
	 *
	 * @exception  NullPointerException
	 *     (unchecked exception) Thrown if <TT>theStream</TT> is null.
	 * @exception  IOException
	 *     Thrown if an I/O error occurred.
	 */
	public abstract PJGImage.Reader prepareToRead
		(InputStream theStream)
		throws IOException;

	/**
	 * Create an instance of class PJGImage based on the given input stream. An
	 * instance of the correct subclass of class PJGImage is created, depending
	 * on the contents of the input stream. The image's header information is
	 * read from the input stream, then the input stream is reset to its
	 * position when <TT>createFromStream()</TT> was called. The input stream is
	 * not closed.
	 * <P>
	 * The <TT>createFromStream()</TT> method calls the <TT>mark()</TT> and
	 * <TT>reset()</TT> methods on the input stream. If the input stream does
	 * not support these methods, an IOException is thrown.
	 * <P>
	 * For improved performance, specify an input stream with buffering, such
	 * as an instance of class java.io.BufferedInputStream. (BufferedInputStream
	 * supports <TT>mark()</TT> and <TT>reset()</TT>.)
	 *
	 * @param  theStream  Input stream.
	 *
	 * @return  PJG image object.
	 *
	 * @exception  NullPointerException
	 *     (unchecked exception) Thrown if <TT>theStream</TT> is null.
	 * @exception  IOException
	 *     Thrown if an I/O error occurred.
	 */
	public static PJGImage createFromStream
		(InputStream theStream)
		throws IOException
		{
		// Mark input stream. Assume we will need to read at most 100 bytes to
		// determine the image type.
		if (! theStream.markSupported())
			{
			throw new IOException
				("PJGImage.createImage(): Input stream does not support mark() and reset()");
			}
		theStream.mark (100);

		// Read and verify header segment.
		DataInputStream dis = new DataInputStream (theStream);
		byte[] hdr = new byte [HEADER.length];
		dis.readFully (hdr);
		if (! Arrays.equals (hdr, HEADER))
			{
			throw new PJGImageFileFormatException ("Invalid PJG header");
			}

		// Read segments until we have the image type.
		int imageType = -1;
		while (imageType == -1)
			{
			int segmentType = dis.readUnsignedByte();
			switch (segmentType)
				{
				case SEGMENT_IMAGE_TYPE:
					imageType = dis.readUnsignedByte();
					break;
				case SEGMENT_HEIGHT:
					dis.readInt();
					break;
				case SEGMENT_WIDTH:
					dis.readInt();
					break;
				case SEGMENT_CREATION_TIME:
					dis.readLong();
					break;
				case SEGMENT_COMMENT:
					dis.readUTF();
					break;
				}
			}

		// Restore stream to original position.
		theStream.reset();

		// Return an instance of the correct subclass.
		switch (imageType)
			{
			case IMAGE_TYPE_24_BIT_COLOR:
				return new PJGColorImage();
			case IMAGE_TYPE_8_BIT_GRAY:
				return new PJGGrayImage();
			case IMAGE_TYPE_24_BIT_HUE:
				return new PJGHueImage();
			default:
				throw new PJGImageFileFormatException
					("Invalid PJG image type (= " + imageType + ")");
			}
		}

	/**
	 * Create an instance of class PJGImage and read the image's pixel data from
	 * the given input stream. An instance of the correct subclass of class
	 * PJGImage is created, depending on the contents of the input stream. The
	 * image's pixel data is read from the input stream, then the input stream
	 * is closed.
	 * <P>
	 * The <TT>readFromStream()</TT> method calls the <TT>mark()</TT> and
	 * <TT>reset()</TT> methods on the input stream. If the input stream does
	 * not support these methods, an IOException is thrown.
	 * <P>
	 * For improved performance, specify an input stream with buffering, such
	 * as an instance of class java.io.BufferedInputStream. (BufferedInputStream
	 * supports <TT>mark()</TT> and <TT>reset()</TT>.)
	 *
	 * @param  theStream  Input stream.
	 *
	 * @return  PJG image object containing image read from <TT>theStream</TT>.
	 *
	 * @exception  NullPointerException
	 *     (unchecked exception) Thrown if <TT>theStream</TT> is null.
	 * @exception  IOException
	 *     Thrown if an I/O error occurred.
	 */
	public static PJGImage readFromStream
		(InputStream theStream)
		throws IOException
		{
		// Create image.
		PJGImage image = createFromStream (theStream);

		// Read pixel data.
		PJGImage.Reader reader = image.prepareToRead (theStream);
		reader.read();
		reader.close();

		// Return image.
		return image;
		}

	/**
	 * Obtain a BufferedImage whose pixel data comes from this image's
	 * underlying matrix.
	 *
	 * @return  BufferedImage.
	 */
	public abstract BufferedImage getBufferedImage();

	/**
	 * Obtain a Displayable object with which to display this image in a Swing
	 * UI.
	 *
	 * @return  Displayable object.
	 */
	public abstract Displayable getDisplayable();

// Hidden operations.

	/**
	 * Set this image's height and width.
	 *
	 * @param  theHeight  Image height in pixels.
	 * @param  theWidth   Image width in pixels.
	 *
	 * @exception  IllegalArgumentException
	 *     (unchecked exception) Thrown if <TT>theHeight</TT> &lt;= 0. Thrown if
	 *     <TT>theWidth</TT> &lt;= 0.
	 */
	void setHeightAndWidth
		(int theHeight,
		 int theWidth)
		{
		if (theHeight <= 0)
			{
			throw new IllegalArgumentException
				("PJGImage.setHeightAndWidth(): theHeight = " + theHeight +
				 " illegal");
			}
		if (theWidth <= 0)
			{
			throw new IllegalArgumentException
				("PJGImage.setHeightAndWidth(): theWidth = " + theWidth +
				 " illegal");
			}
		myHeight = theHeight;
		myWidth = theWidth;
		}

	/**
	 * Write this image's header, image type, height, width, creation time, and
	 * comment segments to the given data output stream.
	 *
	 * @param  theDos        Data output stream.
	 *
	 * @exception  IOException
	 *     Thrown if an I/O error occurred.
	 */
	void writeHeader
		(DataOutputStream theDos)
		throws IOException
		{
		// Header segment.
		theDos.write (HEADER);

		// Image type segment.
		theDos.writeByte (SEGMENT_IMAGE_TYPE);
		theDos.writeByte (myImageType);

		// Height segment.
		theDos.writeByte (SEGMENT_HEIGHT);
		theDos.writeInt (myHeight);

		// Width segment.
		theDos.writeByte (SEGMENT_WIDTH);
		theDos.writeInt (myWidth);

		// Creation time segment.
		if (myCreationTime != null)
			{
			theDos.writeByte (SEGMENT_CREATION_TIME);
			theDos.writeLong (myCreationTime);
			}

		// Comment segments.
		for (String comment : myComments)
			{
			theDos.writeByte (SEGMENT_COMMENT);
			theDos.writeUTF (comment);
			}
		}

	/**
	 * Read the header segment from the given data input stream. If the header
	 * is not correct, an exception is thrown.
	 *
	 * @param  theDis  Data input stream.
	 *
	 * @return  Segment type of the next segment after the header segment, or -1
	 *          if at end of stream.
	 *
	 * @exception  IOException
	 *     Thrown if an I/O error occurred.
	 */
	int readHeader
		(DataInputStream theDis)
		throws IOException
		{
		// Read and verify header segment.
		byte[] hdr = new byte [HEADER.length];
		theDis.readFully (hdr);
		if (! Arrays.equals (hdr, HEADER))
			{
			throw new PJGImageFileFormatException ("Invalid PJG header");
			}

		// Next segment type.
		return theDis.read();
		}

	/**
	 * Read the image type segment from the given data input stream. If the
	 * value read from the input stream does not equal this image's image type,
	 * an exception is thrown.
	 *
	 * @param  theDis  Data input stream.
	 *
	 * @return  Segment type of the next segment after the image type segment,
	 *          or -1 if at end of stream.
	 *
	 * @exception  IOException
	 *     Thrown if an I/O error occurred.
	 */
	int readImageType
		(DataInputStream theDis)
		throws IOException
		{
		// Read and verify image type segment.
		int t = theDis.readUnsignedByte();
		if (t != myImageType)
			{
			throw new PJGImageFileFormatException
				("Invalid PJG image type: required = " + myImageType +
				 ", found = " + t);
			}

		// Next segment type.
		return theDis.read();
		}

	/**
	 * Read the height segment from the given data input stream. If this image's
	 * height is uninitialized, this image's height is set to the value read
	 * from the input stream. If this image's height is initialized, and the
	 * value read from the input stream is not the same, an exception is thrown.
	 *
	 * @param  theDis  Data input stream.
	 *
	 * @return  Segment type of the next segment after the height segment, or -1
	 *          if at end of stream.
	 *
	 * @exception  IOException
	 *     Thrown if an I/O error occurred.
	 */
	int readHeight
		(DataInputStream theDis)
		throws IOException
		{
		// Read and verify image height segment.
		int h = theDis.readInt();
		if (h <= 0)
			{
			throw new PJGImageFileFormatException
				("Invalid PJG image height (= " + h + ")");
			}
		if (myHeight == 0)
			{
			myHeight = h;
			}
		else if (h != myHeight)
			{
			throw new PJGImageFileFormatException
				("Invalid PJG image height: required = " + myHeight +
				 ", found = " + h);
			}

		// Next segment type.
		return theDis.read();
		}

	/**
	 * Read the width segment from the given data input stream. If this image's
	 * width is uninitialized, this image's width is set to the value read
	 * from the input stream. If this image's width is initialized, and the
	 * value read from the input stream is not the same, an exception is thrown.
	 *
	 * @param  theDis  Data input stream.
	 *
	 * @return  Segment type of the next segment after the width segment, or -1
	 *          if at end of stream.
	 *
	 * @exception  IOException
	 *     Thrown if an I/O error occurred.
	 */
	int readWidth
		(DataInputStream theDis)
		throws IOException
		{
		// Read and verify image width segment.
		int w = theDis.readInt();
		if (w <= 0)
			{
			throw new PJGImageFileFormatException
				("Invalid PJG image width (= " + w + ")");
			}
		if (myWidth == 0)
			{
			myWidth = w;
			}
		else if (w != myWidth)
			{
			throw new PJGImageFileFormatException
				("Invalid PJG image width: required = " + myWidth +
				 ", found = " + w);
			}

		// Next segment type.
		return theDis.read();
		}

	/**
	 * Read the creation time segment from the given data input stream.
	 *
	 * @param  theDis  Data input stream.
	 *
	 * @return  Segment type of the next segment after the creation time
	 *          segment, or -1 if at end of stream.
	 *
	 * @exception  IOException
	 *     Thrown if an I/O error occurred.
	 */
	int readCreationTime
		(DataInputStream theDis)
		throws IOException
		{
		// Read and verify image creation time segment.
		myCreationTime = theDis.readLong();

		// Next segment type.
		return theDis.read();
		}

	/**
	 * Read the comment segment from the given data input stream.
	 *
	 * @param  theDis  Data input stream.
	 *
	 * @return  Segment type of the next segment after the comment segment, or
	 *          -1 if at end of stream.
	 *
	 * @exception  IOException
	 *     Thrown if an I/O error occurred.
	 */
	int readComment
		(DataInputStream theDis)
		throws IOException
		{
		// Read and verify image comment segment.
		myComments.add (theDis.readUTF());

		// Next segment type.
		return theDis.read();
		}

// Exported helper classes.

	/**
	 * Class PJGImage.Writer is the abstract base class for an object with which
	 * to write a {@linkplain PJGImage} to an output stream.
	 * <P>
	 * When a PJG image writer is created, the header, image type, height,
	 * width, creation time, and comment segments are written to the output
	 * stream. Each time the <TT>write()</TT>, <TT>writeRowSlice()</TT>,
	 * <TT>writeColSlice()</TT>, or <TT>writePatch()</TT> method is called, one
	 * pixel data segment is written to the output stream. The type of pixel
	 * data segment depends on the PJG image subclass. When finished, call the
	 * <TT>close()</TT> method.
	 * <P>
	 * <I>Note:</I> Class PJGImage.Writer is not multiple thread safe.
	 *
	 * @author  Alan Kaminsky
	 * @version 31-Oct-2007
	 */
	public abstract class Writer
		{

	// Hidden data members.

		OutputStream myOs;
		DataOutputStream myDos;

	// Hidden constructors.

		/**
		 * Construct a new PJG image writer.
		 *
		 * @param  theStream  Output stream.
		 *
		 * @exception  NullPointerException
		 *     (unchecked exception) Thrown if <TT>theStream</TT> is null.
		 * @exception  IOException
		 *     Thrown if an I/O error occurred.
		 */
		Writer
			(OutputStream theStream)
			throws IOException
			{
			if (theStream == null)
				{
				throw new NullPointerException
					("PJGImage.Writer(): theStream is null");
				}
			myOs = theStream;
			myDos = new DataOutputStream (theStream);
			writeHeader (myDos);
			}

	// Exported operations.

		/**
		 * Write all rows and columns of the image to the output stream.
		 *
		 * @exception  IOException
		 *     Thrown if an I/O error occurred.
		 */
		public abstract void write()
			throws IOException;

		/**
		 * Write the given row slice of the image to the output stream. Pixels
		 * in the given range of rows and in all columns are written.
		 * <P>
		 * <I>Note:</I> <TT>theRowRange</TT>'s stride must be 1.
		 *
		 * @param  theRowRange  Range of pixel rows.
		 *
		 * @exception  NullPointerException
		 *     (unchecked exception) Thrown if <TT>theRowRange</TT> is null.
		 * @exception  IllegalArgumentException
		 *     (unchecked exception) Thrown if <TT>theRowRange</TT>'s stride is
		 *     greater than 1.
		 * @exception  IndexOutOfBoundsException
		 *     (unchecked exception) Thrown if any index in <TT>theRowRange</TT>
		 *     is outside the range 0 .. image height - 1.
		 * @exception  IOException
		 *     Thrown if an I/O error occurred.
		 */
		public abstract void writeRowSlice
			(Range theRowRange)
			throws IOException;

		/**
		 * Write the given column slice of the image to the output stream.
		 * Pixels in all rows and in the given range of columns are written.
		 * <P>
		 * <I>Note:</I> <TT>theColRange</TT>'s stride must be 1.
		 *
		 * @param  theColRange  Range of pixel columns.
		 *
		 * @exception  NullPointerException
		 *     (unchecked exception) Thrown if <TT>theColRange</TT> is null.
		 * @exception  IllegalArgumentException
		 *     (unchecked exception) Thrown if <TT>theColRange</TT>'s stride is
		 *     greater than 1.
		 * @exception  IndexOutOfBoundsException
		 *     (unchecked exception) Thrown if any index in <TT>theColRange</TT>
		 *     is outside the range 0 .. image width - 1.
		 * @exception  IOException
		 *     Thrown if an I/O error occurred.
		 */
		public abstract void writeColSlice
			(Range theColRange)
			throws IOException;

		/**
		 * Write the given patch of the image to the output stream. Pixels in
		 * the given range of rows and in the given range of columns are
		 * written.
		 * <P>
		 * <I>Note:</I> <TT>theRowRange</TT>'s stride must be 1.
		 * <TT>theColRange</TT>'s stride must be 1.
		 *
		 * @param  theRowRange  Range of pixel rows.
		 * @param  theColRange  Range of pixel columns.
		 *
		 * @exception  NullPointerException
		 *     (unchecked exception) Thrown if <TT>theRowRange</TT> is null.
		 *     Thrown if <TT>theColRange</TT> is null.
		 * @exception  IllegalArgumentException
		 *     (unchecked exception) Thrown if <TT>theRowRange</TT>'s stride is
		 *     greater than 1. Thrown if <TT>theColRange</TT>'s stride is
		 *     greater than 1.
		 * @exception  IndexOutOfBoundsException
		 *     (unchecked exception) Thrown if any index in <TT>theRowRange</TT>
		 *     is outside the range 0 .. image height - 1. Thrown if any index
		 *     in <TT>theColRange</TT> is outside the range 0 .. image width -
		 *     1.
		 * @exception  IOException
		 *     Thrown if an I/O error occurred.
		 */
		public abstract void writePatch
			(Range theRowRange,
			 Range theColRange)
			throws IOException;

		/**
		 * Close the output stream.
		 *
		 * @exception  IOException
		 *     Thrown if an I/O error occurred.
		 */
		public void close()
			throws IOException
			{
			myDos.close();
			}

		}

	/**
	 * Class PJGImage.Reader is the abstract base class for an object with which
	 * to read a {@linkplain PJGImage} from an input stream.
	 * <P>
	 * When a PJG image reader is created, the header, image type, height,
	 * width, creation time, and comment segments are read from the input
	 * stream.
	 * <P>
	 * To read the pixel data segments one at a time, first call the
	 * <TT>getRowRange()</TT> and <TT>getColRange()</TT> methods to obtain the
	 * range of rows and columns in the next pixel data segment. At this point,
	 * allocate storage for the rows and columns in the underlying matrix if
	 * necessary. Then call the <TT>readSegment()</TT> method to read the actual
	 * pixel data. Repeat these steps if there are additional pixel data
	 * segments.
	 * <P>
	 * To read all the pixel data segments (or all the remaining pixel data
	 * segments), call the <TT>read()</TT> method.
	 * <P>
	 * When finished, call the <TT>close()</TT> method.
	 * <P>
	 * <I>Note:</I> Class PJGImage.Reader is not multiple thread safe.
	 *
	 * @author  Alan Kaminsky
	 * @version 01-Nov-2007
	 */
	public abstract class Reader
		{

	// Hidden data members.

		InputStream myIs;
		DataInputStream myDis;
		int myNextSegmentType;

	// Hidden constructors.

		/**
		 * Construct a new PJG image reader.
		 *
		 * @param  theStream  Input stream.
		 *
		 * @exception  NullPointerException
		 *     (unchecked exception) Thrown if <TT>theStream</TT> is null.
		 * @exception  IOException
		 *     Thrown if an I/O error occurred.
		 */
		Reader
			(InputStream theStream)
			throws IOException
			{
			if (theStream == null)
				{
				throw new NullPointerException
					("PJGImage.Reader(): theStream is null");
				}
			myIs = theStream;
			myDis = new DataInputStream (theStream);

			// Read and verify segments down to first pixel data segment.
			boolean imageTypeSeen = false;
			boolean heightSeen = false;
			boolean widthSeen = false;
			boolean creationTimeSeen = false;
			myNextSegmentType = readHeader (myDis);
			segmentLoop : for (;;)
				{
				switch (myNextSegmentType)
					{
					case SEGMENT_IMAGE_TYPE:
						if (imageTypeSeen)
							{
							throw new PJGImageFileFormatException
								("Invalid PJG file: multiple image type segments");
							}
						myNextSegmentType = readImageType (myDis);
						imageTypeSeen = true;
						break;
					case SEGMENT_HEIGHT:
						if (heightSeen)
							{
							throw new PJGImageFileFormatException
								("Invalid PJG file: multiple height segments");
							}
						myNextSegmentType = readHeight (myDis);
						heightSeen = true;
						break;
					case SEGMENT_WIDTH:
						if (widthSeen)
							{
							throw new PJGImageFileFormatException
								("Invalid PJG file: multiple width segments");
							}
						myNextSegmentType = readWidth (myDis);
						widthSeen = true;
						break;
					case SEGMENT_CREATION_TIME:
						if (creationTimeSeen)
							{
							throw new PJGImageFileFormatException
								("Invalid PJG file: multiple creation time segments");
							}
						myNextSegmentType = readCreationTime (myDis);
						creationTimeSeen = true;
						break;
					case SEGMENT_COMMENT:
						myNextSegmentType = readComment (myDis);
						break;
					default:
						break segmentLoop;
					}
				}
			if (! imageTypeSeen)
				{
				throw new PJGImageFileFormatException
					("Invalid PJG file: missing image type segment");
				}
			if (! heightSeen)
				{
				throw new PJGImageFileFormatException
					("Invalid PJG file: missing height segment");
				}
			if (! widthSeen)
				{
				throw new PJGImageFileFormatException
					("Invalid PJG file: missing width segment");
				}
			}

	// Exported operations.

		/**
		 * Read all pixel data segments from the input stream. If some pixel
		 * data segments have already been read, the <TT>read()</TT> method
		 * reads all remaining pixel data segments. If there are no more pixel
		 * data segments, the <TT>read()</TT> method does nothing. If storage is
		 * not already allocated in the underlying matrix for the pixel rows and
		 * columns in a pixel data segment, the <TT>read()</TT> method allocates
		 * the necessary storage.
		 *
		 * @exception  IOException
		 *     Thrown if an I/O error occurred.
		 */
		public abstract void read()
			throws IOException;

		/**
		 * Obtain the row range of the next pixel data segment in the input
		 * stream. If there are no more pixel data segments, null is returned.
		 *
		 * @return  Row range, or null.
		 *
		 * @exception  IOException
		 *     Thrown if an I/O error occurred.
		 */
		public abstract Range getRowRange()
			throws IOException;

		/**
		 * Obtain the column range of the next pixel data segment in the input
		 * stream. If there are no more pixel data segments, null is returned.
		 *
		 * @return  Column range, or null.
		 *
		 * @exception  IOException
		 *     Thrown if an I/O error occurred.
		 */
		public abstract Range getColRange()
			throws IOException;

		/**
		 * Read the next pixel data segment from the input stream. If there are
		 * no more pixel data segments, the <TT>readSegment()</TT> method does
		 * nothing. If storage is not already allocated in the underlying matrix
		 * for the pixel rows and columns in the pixel data segment, the
		 * <TT>readSegment()</TT> method allocates the necessary storage.
		 *
		 * @exception  IOException
		 *     Thrown if an I/O error occurred.
		 */
		public abstract void readSegment()
			throws IOException;

		/**
		 * Close the input stream.
		 *
		 * @exception  IOException
		 *     Thrown if an I/O error occurred.
		 */
		public void close()
			throws IOException
			{
			myDis.close();
			}

		}

	}
